package myicesscheduler.web;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.multipart.MultipartFile;

// folder means directory name without filename.
// path means directory name with filename.
public class MyFileUtil
{
   private static final Log logger = LogFactory.getLog( MyFileUtil.class );

   // return: path after upload folder, eg: "digestedValue/filename"
   // The digested value is used as sub folder to avoid same filename confliction.
   public static String storeFile( MultipartFile multiPartFile, MyFolderInterface whereToStore )
   {
      if( null == multiPartFile ) throw new IllegalArgumentException();

      final String filename = multiPartFile.getOriginalFilename();
      if( null == filename ) throw new IllegalArgumentException();
      if( "".equals( filename ) ) throw new IllegalArgumentException();

      final String delim = String.valueOf( File.separatorChar );
      if( filename.contains( delim ) ) throw new IllegalArgumentException();
      logger.info( "Store file: " + filename );

      byte[] data;
      try
      {
         data = multiPartFile.getBytes();
      } catch( IOException e )
      {
         e.printStackTrace();
         throw new IllegalArgumentException();
      }

      String subFolderName;
      try
      {
         subFolderName = getSubFolderName( filename, data );
      } catch( final FileAlreadyUploadedException e )
      {
         return e.getPathAfterUpload();
      }

      if( false == whereToStore.isThereFolder( subFolderName ) )
      {
         final boolean bCreateFolder = whereToStore.createFolder( subFolderName );
         if( false == bCreateFolder )
            throw new RuntimeException( "A new folder cannot be created: " + subFolderName );
      }

      final String pathAfterUpload = getPath( subFolderName, filename );
      final boolean bStored = whereToStore.storeFile( pathAfterUpload, data );
      if( false == bStored )
      {
         whereToStore.removeFile( pathAfterUpload );
         whereToStore.removeFolderIfEmpty( subFolderName );
         throw new RuntimeException( "File cannot be stored: " + pathAfterUpload );
      }

      return pathAfterUpload;
   }

   static String getSubFolderName( String filename, byte[] contents )
         throws FileAlreadyUploadedException
   {
      final String digestedValue = getDigestedValue( contents );
      return getSubFolderName( digestedValue, filename, contents );
   }

   private static String getSubFolderName( String digestedValue, String filename, byte[] contents )
         throws FileAlreadyUploadedException
   {
      String subFolderName = digestedValue;

      String pathAfterUpload;
      while( MyFolderUtil.uploadFolder.isThereFile( pathAfterUpload = getPath( subFolderName,
            filename ) ) )
      {
         if( MyFolderUtil.uploadFolder.isSameFile( pathAfterUpload, contents ) )
            throw new FileAlreadyUploadedException( subFolderName, filename );

         subFolderName += "_";
      }

      return subFolderName;
   }

   @SuppressWarnings( "serial" )
   static class FileAlreadyUploadedException extends Throwable
   {
      public FileAlreadyUploadedException( String subFolderName, String filename )
      {
         subFolderName_ = subFolderName;
         filename_ = filename;
      }

      public String getPathAfterUpload()
      {
         return getPath( subFolderName_, filename_ );
      }

      public String getSubFolderName()
      {
         return subFolderName_;
      }

      private String subFolderName_, filename_;
   }

   public static String getPath( String folderName, String filename )
   {
      final String delim = String.valueOf( File.separatorChar );
      final String path = folderName + delim + filename;
      return path;
   }

   public static String getCanonicalPath( String folderName, String filename )
   {
      final String path = getPath( folderName, filename );
      final File pathFH = new File( path );
      if( !pathFH.exists() ) return path;

      String canonicalPath;
      try
      {
         canonicalPath = pathFH.getCanonicalPath();
      } catch( IOException e )
      {
         e.printStackTrace();
         return path;
      }
      return canonicalPath;
   }

   /**
    * This Digested value can be used as sub folder name that allows to
    * store the files whose names are same but their contents are different.
    * @param multiPartFile
    * @return sub folder name
    */
   static String getDigestedValue( byte[] data )
   {
      int digestedValue = 0;
      for( final byte aByte : data )
         digestedValue += aByte;

      return String.valueOf( digestedValue );
   }

   static boolean storeFile( String fullpath, byte[] bytes )
   {
      logger.info( "Store file: " + fullpath );
      FileOutputStream fos = null;
      try
      {
         fos = new FileOutputStream( fullpath );
      } catch( FileNotFoundException e )
      {
         e.printStackTrace();
         return false;
      }

      try
      {
         fos.write( bytes );
      } catch( IOException e )
      {
         e.printStackTrace();
         return false;
      } finally
      {
         try
         {
            fos.close();
         } catch( IOException e )
         {
            e.printStackTrace();
            return false;
         }
      }
      return true;
   }

   public static String getFilenameOnly( String pathname )
   {
      return new StructuredPathname( pathname ).getFilenameOnly();
   }

   public static String[] getFullPathFoldernames( String pathname )
   {
      return new StructuredPathname( pathname ).getFullPathFoldernames();
   }

   public static String[] getFoldernamesOnly( String pathname )
   {
      return new StructuredPathname( pathname ).getFoldernamesOnly();
   }

   static class StructuredPathname
   {
      final String filenameOnly;
      final String foldernameOnly;
      final String[] foldernamesOnly;
      final String[] fullPathFoldernames;

      public StructuredPathname( String pathname )
      {
         final String[] delims = new String[] { String.valueOf( File.separatorChar ), "/", "\\" };
         for( final String delim : delims )
         {
            final int idx = pathname.lastIndexOf( delim );
            if( idx < 0 ) continue;

            this.foldernameOnly = pathname.substring( 0, idx );
            this.filenameOnly = pathname.substring( idx + 1 );

            final List< String > fullPathFolderNames = new LinkedList< String >();
            final List< String > foldernamesOnly = new LinkedList< String >();

            String parentFoldername = "";
            for( final String subfoldername : foldernameOnly.split( delim ) )
            {
               if( subfoldername.equals( "" ) ) continue;
               
               foldernamesOnly.add( subfoldername );

               final String childFoldername = parentFoldername + subfoldername;
               fullPathFolderNames.add( childFoldername );

               parentFoldername += subfoldername + delim;
            }

            this.fullPathFoldernames = fullPathFolderNames.toArray( new String[ fullPathFolderNames
                  .size() ] );
            this.foldernamesOnly = foldernamesOnly.toArray( new String[ foldernamesOnly.size() ] );
            return;
         }

         // folder name not found
         this.fullPathFoldernames = new String[] {};
         this.foldernamesOnly = new String[] {};
         this.foldernameOnly = "";
         this.filenameOnly = pathname;
      }

      public String getFilenameOnly()
      {
         return filenameOnly;
      }

      public String[] getFullPathFoldernames()
      {
         return fullPathFoldernames;
      }

      public String[] getFoldernamesOnly()
      {
         return foldernamesOnly;
      }
   }

   public static byte[] readFile( String fullpath )
   {
      if( false == MyFileUtil.isThereFile( fullpath ) ) return null;

      final long sizeofFile = new File( fullpath ).length();
      final byte[] rst = new byte[ (int) sizeofFile ];

      FileInputStream in = null;
      try
      {
         in = new FileInputStream( fullpath );
      } catch( FileNotFoundException e1 )
      {
         e1.printStackTrace();
         return null;
      }

      try
      {
         for( int i = 0, c = 0; i < sizeofFile && ( c = in.read() ) != -1; ++i )
            rst[ i ] = (byte) c;
      } catch( IOException e )
      {
         e.printStackTrace();
         return null;
      } finally
      {
         try
         {
            in.close();
         } catch( IOException e )
         {
            e.printStackTrace();
            return null;
         }
      }

      return rst;
   }

   static boolean removeFile( String fullpath )
   {
      final File file = new File( fullpath );
      if( null == file ) return false;
      if( false == file.exists() ) return true;
      if( false == file.canWrite() ) return false;
      if( file.isDirectory() ) return false;
      return file.delete();
   }

   static boolean createFolder( String absoluteFoldername )
   {
      final File file = new File( absoluteFoldername );
      if( null == file ) return false;
      if( file.exists() ) return false;
      return file.mkdir();
   }

   static boolean removeFolderIfEmpty( String absoluteFoldername )
   {
      final File file = new File( absoluteFoldername );
      if( null == file ) return false;
      if( false == file.exists() ) return true;
      if( false == file.canWrite() ) return false;
      if( false == file.isDirectory() ) return false;
      if( 0 != file.list().length ) return false;
      return file.delete();
   }

   public static boolean isThereFile( String fullpath )
   {
      final File file = new File( fullpath );
      if( false == file.exists() ) return false;
      if( file.isDirectory() ) return false;
      return true;
   }

   public static boolean isThereFolder( String fullpath )
   {
      final File file = new File( fullpath );
      if( false == file.exists() ) return false;
      if( false == file.isDirectory() ) return false;
      return true;
   }

   public static boolean isSameFile( String fullpath, byte[] data )
   {
      final byte[] dataFromFile = readFile( fullpath );
      if( data.length != dataFromFile.length ) return false;
      for( int i = 0; i < data.length; ++i )
         if( data[ i ] != dataFromFile[ i ] ) return false;
      return true;
   }

   // eg: baseFolder = "/Usr", fullPath = "/Usr/a/b.txt", then the result = "a/b.txt"
   public static String getSubPath( String baseFolder, String fullPath )
   {
      final String[] baseFolders = MyFileUtil.getFoldernamesOnly( getPath( baseFolder, "" ) );
      final String[] fullFolders = MyFileUtil.getFoldernamesOnly( fullPath );
      final String filename = MyFileUtil.getFilenameOnly( fullPath );

      if( baseFolders.length > fullFolders.length )
         throw new RuntimeException( "Invalid baseFolder: " + baseFolder );

      for( int i = 0; i < baseFolders.length; ++i )
      {
         if( baseFolders[ i ].equals( fullFolders[ i ] ) ) continue;
         throw new RuntimeException( "Invalid baseFolder: " + baseFolder );
      }

      String rst = "";
      for( int i = baseFolders.length; i < fullFolders.length; ++i )
      {
         final String eachFolder = fullFolders[ i ];
         rst = rst.equals( "" ) ? eachFolder : getPath( rst, eachFolder );
      }
      return getPath( rst, filename );
   }
}
